﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace XNAssist
{
    /// <summary>
    /// A list that holds a "buffer" that allows it to add or remove items AFTER you iterate through it in a game loop
    /// </summary>
    /// <typeparam name="TItemType"> The type of item this list is holding </typeparam>
    /// <remarks> DON'T FORGET TO CALL THE UPDATE METHOD IN THE GAME LOOP </remarks>
    public class BufferedList<TItemType> : IEnumerable<TItemType>
    {
        private readonly List<TItemType> _items;

        private readonly List<TItemType> _toAdd, _toRemove;

        /// <summary>
        /// A simple constructor
        /// </summary>
        public BufferedList()
        {
            _items = new List<TItemType>();
            _toAdd = new List<TItemType>();
            _toRemove = new List<TItemType>();
        }

        /// <summary>
        /// An indexer: note that this only accesses items that are already added, and does not account for the buffered adding/removed items.
        /// </summary>
        /// <param name="index"> The index of the item to get or set </param>
        /// <returns> The item found at the given index </returns>
        public TItemType this[int index]
        {
            get { return _items[index]; }
            set { _items[index] = value; }
        }

        /// <summary>
        /// Gets the count of this list (does account for the adding buffer, but not the removal buffer)
        /// </summary>
        public int Count
        {
            get
            {
                return _items.Count(item => !_toRemove.Contains(item)) + _toAdd.Count(item => !_toRemove.Contains(item));
            }
        }

        /// <summary>
        /// Updates the list by applying the buffers: call this in a game loop. After you do everything else is a good idea.
        /// </summary>
        public void Update()
        {
            foreach (var item in _toRemove)
                _items.Remove(item);

            _items.AddRange(_toAdd);

            _toRemove.Clear();
            _toAdd.Clear();
        }

        /// <summary>
        /// Adds an item to the list's buffer, so it can be added the next time Update is called
        /// </summary>
        /// <param name="item"> The item to add </param>
        public void Add(TItemType item)
        {
            _toAdd.Add(item);
        }

        /// <summary>
        /// Adds an item to this list directly, bypassing the buffer (WARNING: NOT ITERATOR-SAFE!)
        /// </summary>
        /// <param name="item"> The item to add </param>
        public void AddDirect(TItemType item)
        {
            _items.Add(item);
        }

        /// <summary>
        /// Moves all items in this list into the removal buffer
        /// </summary>
        public void Clear()
        {
            foreach (var item in _items)
                Remove(item);

            _toAdd.Clear();
        }

        /// <summary>
        /// Clears all items in this list instantly, bypassing the buffer (WARNING: NOT ITERATOR-SAFE!)
        /// </summary>
        public void ClearDirect()
        {
            _items.Clear();
            _toAdd.Clear();
            _toRemove.Clear();
        }

        /// <summary>
        /// Checks whether this list contains the given item
        /// </summary>
        /// <param name="item"> The item to check for </param>
        /// <returns> Whether the item is contained in this list </returns>
        public bool Contains(TItemType item)
        {
            return (_items.Contains(item) || _toAdd.Contains(item)) && !_toRemove.Contains(item);
        }

        /// <summary>
        /// Removes the given item from this list by putting it into a removal buffer
        /// </summary>
        /// <param name="item"> The item to remove </param>
        public void Remove(TItemType item)
        {
            _toRemove.Add(item);
        }

        /// <summary>
        /// Removes the given item from this list instantly, bypassing the buffer (WARNING: NOT ITERATOR-SAFE!)
        /// </summary>
        /// <param name="item"> The item to remove </param>
        public void RemoveDirect(TItemType item)
        {
            _items.Remove(item);
        }

        /// <summary>
        /// Finds what index the given item is at
        /// </summary>
        /// <param name="item"> The item to find the index of </param>
        /// <returns> The index of the item </returns>
        public int IndexOf(TItemType item)
        {
            return _items.IndexOf(item);
        }

        /// <summary>
        /// Gets the enumerator for this list
        /// </summary>
        /// <returns> The enumerator for this list </returns>
        public IEnumerator<TItemType> GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        /// <summary>
        /// Gets the enumerator for this list
        /// </summary>
        /// <returns> The enumerator for this list </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
